//customized Layered Pane explicitly for use in Program Frame of Checkers3D

//import only what we need
import javax.swing.JLayeredPane;
import javax.swing.Timer;
import javax.swing.KeyStroke;
import javax.swing.Action;
import java.awt.Rectangle;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;

class ManagedLayeredPane extends JLayeredPane implements Action {
	
	//declare some class vars
	// an instance of popup layer
	private PopupLayer popuplay;
	// an instance of console layer
	private ConsoleLayer consolelay;
	// a Timer for handling smooth popping out of dynamic layers
	private Timer ticker;
	// a mesaure of refresh rate
	private int tickpersecond;
	// (time to delay between refreshing component positions)
	private int tickdelay;
	// a measure of speed for dynamic layers that move
	// (number of pixels to move each time dynamic component is to be moved)
	private int speed;
	// a rectangle to hold the previous bounds
	private Rectangle prevbounds;
	// a flag to quickly keep track if console is currently visible
	private boolean showConsole;
	// a flag to quickly keep track if popup is currently visible
	private boolean showPopup;
	// a flag to keep track of the first painting of children
	private boolean childrenpainted;
	// a flag to keep track if a key is currently down
	//private boolean keydown;
	// a flag to keep track if a mouse button is currently down
	//private boolean mousedown;
	// a counter to keep track of how many ticks have passed by
	//private int tickcount;
	//define some vars related to the Action implementation
	private boolean actionenabled;
	private String actiontooltip;
	private String actioncommand;
	
	//other variables
	private long timesince = 0;
	
	//define constants related to layering
	public static final int VIEW_LAYER = 2;
	public static final int POPUP_LAYER = 4;
	public static final int CONSOLE_LAYER = 6;
	
	public ManagedLayeredPane(double newtps, double newpps, Rectangle bounds) {
		
		//call ancestor
		super();
		
		//setup our bounds
		this.setBounds(bounds);
		this.prevbounds = new Rectangle(bounds);
		
		//instantiate timer
		this.ticker = new Timer(0, this);
		//this.tickcount = 0;
		//set vars
		this.setRefreshRate(newtps);
		this.setPopoutSpeed(newpps);
		this.childrenpainted = false;
		
		//define Action vars
		this.actionenabled = true;
		this.actiontooltip = "toggle-console";
		this.actioncommand = "ToggleConsole";
		
		//add Ctrl-' key command for ToggleConsole
		this.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
			KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, InputEvent.CTRL_DOWN_MASK), "ToggleConsole");
		this.getInputMap(WHEN_FOCUSED).put(
			KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, InputEvent.CTRL_DOWN_MASK), "ToggleConsole");
		this.getActionMap().put("ToggleConsole", this);
		
		/*
		//steal key events
		this.addKeyListener(this);
		this.keydown = false;
		//steal mouse events
		this.addMouseListener(this);
		this.mousedown = false;
		*/
		
		//dont start timer yet
		ticker.stop();
		
	}
	
	//methods used to build popup and console layers
	public void startConsole() {
		
		Rectangle bounds = this.getBounds();
		Rectangle conbounds = new Rectangle();
		conbounds.x = 0;
		conbounds.y = 0;
		conbounds.width = (int)Math.ceil(bounds.width * 0.95);
		conbounds.height = (int)Math.ceil(bounds.height / 3.0);
		//shift bounds so that console is not visible in the start
		this.showConsole = false;
		conbounds.y = conbounds.y - conbounds.height;
		this.consolelay = new ConsoleLayer(conbounds);
		System.out.println("$1MANAGEDLAYEREDPANE: Console bounds=" + this.consolelay.getBounds());
		this.add(this.consolelay, new Integer(CONSOLE_LAYER));
		
	}
	public void startPopup() {
		
		Rectangle bounds = this.getBounds();
		Rectangle popbounds = new Rectangle();
		popbounds.x = bounds.width;
		popbounds.y = bounds.height;
		popbounds.width = (int)Math.ceil(bounds.width / 3.0);
		popbounds.height = (int)Math.ceil(bounds.height / 4.0);
		//shift bounds so that popup is not visible in the start
		this.showPopup = false;
		//nvm, that is already done above
		//popbounds.x = popbounds.x + popbounds.width;
		//popbounds.y = popbounds.y + popbounds.height;
		this.popuplay = new PopupLayer(popbounds);
		System.out.println("$1MANAGEDLAYEREDPANE: Popup bounds=" + this.popuplay.getBounds());
		this.add(this.popuplay, new Integer(POPUP_LAYER));
		
	}
	
	//methods to check if popup and console layers are ready
	public boolean popupReady() {
		
		if (this.popuplay == null){
			return false;
		}else{
			return true;
		}
		
	}
	public boolean consoleReady() {
		
		if (this.consolelay == null){
			return false;
		}else{
			return true;
		}
		
	}
	
	//mutators for some class vars
	public void setRefreshRate(double newtps) {
		
		double defaulttps = 20;
		if (newtps > 0){
			defaulttps = newtps;
		}
		this.tickdelay = (int)Math.ceil(1000 / defaulttps);
		this.tickpersecond = (int)defaulttps;
		ticker.setDelay(this.tickdelay);
		
	}
	public void setPopoutSpeed(double newpps) {
		
		double defaultpps = 5;
		if (newpps > 0){
			defaultpps = newpps;
		}
		this.speed = (int)Math.ceil(defaultpps);
		
	}
	
	//method used to change if the console is shown or not
	public void displayConsole(boolean show) {
		
		this.showConsole = show;
		
	}
	
	//method used to toggle the console being displayed or not
	public void toggleConsole() {
		
		this.showConsole = !this.showConsole;
		System.out.println("MLP: toggling console = " + this.showConsole);
		//we think we might need to get the animation timer running
		if (!this.ticker.isRunning()){
			this.ticker.restart();
		}
		
	}
	
	//method used to add a message to the console
	public void messageConsole(String message) {
		
		this.consolelay.appendMessage(message);
		
	}
	
	//method used to add a message to the popup
	public void messagePopup(String message, double delay) {
		
		this.popuplay.appendMessage(message, delay);
		//ensure that popup is visible
		this.showPopup = true;
		//we think we might need to get the animation timer running
		if (!this.ticker.isRunning()){
			this.ticker.restart();
		}
		
	}
	
	//method used to remove all components in the view layer
	public void removeViews() {
		
		//extract components in view layer
		Component[] items = this.getComponentsInLayer(ManagedLayeredPane.VIEW_LAYER);
		//loop through components
		for (int c=0; c<items.length; ++c){
			//remove component
			this.remove(items[c]);
		}
		
	}
	
	//method used to add a component to the view layer
	public void addView(Component comp) {
		
		//scale the view
		comp.setBounds(this.getBounds());
		this.add(comp, new Integer(VIEW_LAYER));
		System.out.println("MLP: adding view -> " + comp.getClass().getName());
		//view changes
		this.validate();
		this.repaint();
		
	}
	
	//method overridden to perform scaling operations before drawing children
	protected void paintChildren(Graphics g) {
		
		//call ancestor
		super.paintChildren(g);
		//acquire needed variables
		int maxlayer = this.highestLayer();
		int minlayer = this.lowestLayer();
		Rectangle currbounds = this.getBounds();
		double horizratio = currbounds.width / (double)prevbounds.width;
		double vertratio = currbounds.height / (double)prevbounds.height;
		if (childrenpainted){
			if (!currbounds.equals(prevbounds)){
				
				System.out.println("MLP: paint children."
					+ "\n\tcurrent bounds=" + currbounds
					+ "\n -- previous bounds=" + prevbounds
					+ "\n\tvertical ratio=" + vertratio + " -- horizontal ratio=" + horizratio);
				
				//loop through layers
				for (int i=minlayer; i<=maxlayer; ++i){
					//extract components in layer
					Component[] items = this.getComponentsInLayer(i);
					//loop through components
					for (int c=0; c<items.length; ++c){
						//extract bounds from component
						Rectangle itembounds = items[c].getBounds();
						//System.out.println("MLP: item starts as = " + items[c] + " || bounds = " + itembounds);
						//determine the scaling policy to utilize
						//System.out.println("MLP: item layer number is " + i);
						if (i == POPUP_LAYER){
							//System.out.println("MLP: item is in POPUP_LAYER");
							itembounds.x = (int)Math.floor(itembounds.x * horizratio);
							itembounds.y = (int)Math.floor(itembounds.y * vertratio);
						}else if (i == VIEW_LAYER){
							//System.out.println("MLP: item is in VIEW_LAYER");
							itembounds.width = (int)Math.floor(itembounds.width * horizratio);
							itembounds.height = (int)Math.floor(itembounds.height * vertratio);
						}else if (i == CONSOLE_LAYER){
							//System.out.println("MLP: item is in CONSOLE_LAYER");
							if (!this.showConsole){
								itembounds.y = itembounds.y - (int)Math.floor(itembounds.height * (vertratio - 1));
							}else{
								//itembounds.y = itembounds.y - (int)Math.floor(itembounds.height * (1 - vertratio));
							}
							itembounds.width = (int)Math.floor(itembounds.width * horizratio);
							itembounds.height = (int)Math.floor(itembounds.height * vertratio);
						}
						//apply bounds changes
						items[c].setBounds(itembounds);
						//ask changes to be displayed
						items[c].validate();
						//items[c].repaint();
						//System.out.println("MLP: item ends as = " + items[c] + " || bounds = " + itembounds);
					}
				}
			}
		}else{
			childrenpainted = true;
		}
		prevbounds = new Rectangle(currbounds);
		
	}
	
	//hidden convenience method to check if popup or console layers need to be moved
	// to perform a popout or popin
	private boolean moveLayers() {
		
		//temporary variables
		long time1 = 0;
		long time2 = 0;
		
		//break out if console or popup is not yet started
		if (this.consolelay == null || this.popuplay == null){
			//exit immediately and tell to not continue timer
			// because we were called without being ready
			return false;
		}
		//System.out.println("$1MLP: moveLayers");
		//stop the animation timer while we do work
		boolean movedcon = false;
		boolean movedpop = false;
		Rectangle layeredbounds = this.getBounds();
		//check if console needs to be moved
		// if it is shown and not moved down
		// or if it is not shown and not moved up
		//time1 = System.nanoTime();
		Rectangle tmpbounds = this.consolelay.getBounds();
		if (this.showConsole && (tmpbounds.y < 0)){
			tmpbounds.y += this.speed;
			this.consolelay.setBounds(tmpbounds);
			//this.consolelay.setVisible(true);
			//this.consolelay.validate();
			//this.consolelay.repaint();
			movedcon = true;
		}else if(!this.showConsole && (tmpbounds.y > (0 - tmpbounds.height))){
			tmpbounds.y -= this.speed;
			this.consolelay.setBounds(tmpbounds);
			//this.consolelay.setVisible(true);
			//this.consolelay.validate();
			//this.consolelay.repaint();
			movedcon = true;
		}
		//time2 = System.nanoTime();
		//System.out.println("$1MLP: console nanoseconds = " + (time2 - time1));
		//check if popup should still be shown
		//time1 = System.nanoTime();
		this.showPopup = this.popuplay.hasMessage();
		//check if popup needs to be moved
		// if it is shown and not moved in
		// or if it is not shown and not moved out
		tmpbounds = this.popuplay.getBounds();
		//System.out.println("MLP: popup bounds=" + tmpbounds);
		//System.out.println("MLP: layered bounds=" + layeredbounds);
		if (this.showPopup && (tmpbounds.y > (layeredbounds.height - tmpbounds.height) || tmpbounds.x > (layeredbounds.width - tmpbounds.width))){
			tmpbounds.y -= (int)Math.ceil(this.speed * (tmpbounds.height / (double)tmpbounds.width));
			tmpbounds.x -= (int)Math.ceil(this.speed);
			this.popuplay.setBounds(tmpbounds);
			//this.popuplay.setVisible(true);
			//this.popuplay.validate();
			//this.popuplay.repaint();
			movedpop = true;
		}else if (!this.showPopup && (tmpbounds.y < layeredbounds.height || tmpbounds.x < layeredbounds.width)){
			tmpbounds.y += (int)Math.ceil(this.speed * (tmpbounds.height / (double)tmpbounds.width));
			tmpbounds.x += (int)Math.ceil(this.speed);
			this.popuplay.setBounds(tmpbounds);
			//this.popuplay.setVisible(true);
			//this.popuplay.validate();
			//this.popuplay.repaint();
			movedpop = true;
		}
		//time2 = System.nanoTime();
		//System.out.println("$1MLP: popup nanoseconds = " + (time2 - time1));
		//System.out.println("MLP: popup = " + this.popuplay);
		//check if it will help to stop the timer to save cycles
		//if we have not moved anything and the popup is not shown
		if (!movedcon && !movedpop && !this.showPopup){
			//tell not to continue timer
			return false;
		}else{
			//tell to continue timer
			return true;
		}
		
	}
	
	//overridden Action methods
	public void actionPerformed(ActionEvent EVE) {
		
		//temporary variables
		long time1 = 0;
		long time2 = 0;
		
		if (EVE.getSource().equals(ticker) && this.ticker.isRunning()){
			if (this.timesince != 0){
				//System.out.println("$1MLP: nanoseconds since last timer firing = " + (System.nanoTime() - this.timesince));
			}
			//time1 = System.nanoTime();
			//++this.tickcount;
			//stop the timer while we do work
			this.ticker.stop();
			//System.out.println("MANAGEDLAYEREDPANE: movement timer fired");
			boolean start = this.moveLayers();
			//check if we should start the timer back up
			if (start){
				this.ticker.restart();
			}
			//time2 = System.nanoTime();
			//System.out.println("$1MLP: timer firing nanoseconds = " + (time2 - time1));
			//this.timesince = System.nanoTime();
		}else if (this.actionenabled && EVE.getActionCommand() != null && EVE.getActionCommand().equals("ToggleConsole")){
			//the input mapping to action mapping has been activated
			this.toggleConsole();
		}
		
	}
	public void addPropertyChangeListener(PropertyChangeListener listener) {
		System.out.println("MLP: addPropertyChangeListener called but it is not defined yet...");
	}
	public Object getValue(String key) {
		if (key.equals("ActionCommandKey")){
			return this.actioncommand;
		}
		System.out.println("MLP: getValue(" + key + ") called but it is not defined yet...");
		return null;
	}
	public boolean isEnabled() {
		
		return this.actionenabled;
		
	}
	public void putValue(String key, Object value) {
		System.out.println("MLP: putValue called but it is not defined yet...");
	}
	public void removePropertyChangeListener(PropertyChangeListener listener) {
		System.out.println("MLP: removePropertyChangeListener called but it is not defined yet...");
	}
	public void setEnabled(boolean b) {
		
		this.actionenabled = b;
		
	}
	
	/*
	//overridden key listener methods
	public void keyPressed(KeyEvent EVE) {
		
		this.keydown = true;
		System.out.println("MLP: key down");
		
	}
	public void keyTyped(KeyEvent EVE) {}
	public void keyReleased(KeyEvent EVE) {
		
		this.keydown = false;
		System.out.println("MLP: key Released... ->code=" + EVE.getKeyCode());
		//match a key stroke
		if (EVE.getKeyCode() == (KeyEvent.VK_BACK_QUOTE)){
			System.out.println("MLP: key identified as ` ");
			this.toggleConsole();
		}
		
	}
	
	//overridden mouse listener methods
	public void mouseClicked(MouseEvent EVE) {}
	public void mouseEntered(MouseEvent EVE) {}
	public void mouseExited(MouseEvent EVE) {}
	public void mousePressed(MouseEvent EVE) {
		
		this.mousedown = true;
		System.out.println("MLP: mouse down");
		
	}
	public void mouseReleased(MouseEvent EVE) {
		
		this.mousedown = false;
		System.out.println("MLP: mouse up");
		
	}
	*/
	
	
}